package com.search.service.imp;

import java.io.StringReader;
import java.nio.file.Paths;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;

import org.apache.commons.lang3.StringUtils;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.TokenStream;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field.Store;
import org.apache.lucene.document.IntField;
import org.apache.lucene.document.LongField;
import org.apache.lucene.document.NumericDocValuesField;
import org.apache.lucene.document.SortedDocValuesField;
import org.apache.lucene.document.StringField;
import org.apache.lucene.document.TextField;
import org.apache.lucene.facet.FacetField;
import org.apache.lucene.facet.Facets;
import org.apache.lucene.facet.FacetsCollector;
import org.apache.lucene.facet.FacetsConfig;
import org.apache.lucene.facet.LabelAndValue;
import org.apache.lucene.facet.taxonomy.FastTaxonomyFacetCounts;
import org.apache.lucene.facet.taxonomy.TaxonomyReader;
import org.apache.lucene.facet.taxonomy.directory.DirectoryTaxonomyReader;
import org.apache.lucene.facet.taxonomy.directory.DirectoryTaxonomyWriter;
import org.apache.lucene.index.DirectoryReader;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.index.Term;
import org.apache.lucene.queryparser.classic.MultiFieldQueryParser;
import org.apache.lucene.search.BooleanClause.Occur;
import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.NumericRangeQuery;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.search.Sort;
import org.apache.lucene.search.SortField;
import org.apache.lucene.search.TermQuery;
import org.apache.lucene.search.TopDocs;
import org.apache.lucene.search.TopFieldCollector;
import org.apache.lucene.search.highlight.Highlighter;
import org.apache.lucene.search.highlight.QueryScorer;
import org.apache.lucene.search.highlight.SimpleFragmenter;
import org.apache.lucene.search.highlight.SimpleHTMLFormatter;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
import org.apache.lucene.util.BytesRef;
import org.springframework.stereotype.Service;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;

import com.search.dao.DocDao;
import com.search.entity.Doc;
import com.search.entity.Search;
import com.search.entity.SearchConfig;
import com.search.util.JsonDateValueProcessor;
import com.search.util.PageUtil;
import com.search.util.PropertyUtil;
import com.search.util.StringUtil;

import net.sf.json.JSONArray;
import net.sf.json.JSONObject;
import net.sf.json.JSONSerializer;
import net.sf.json.JsonConfig;

@Service
public class IndexUtil {

	@Resource
	SearchConfig sc;

	@Resource
	DocDao d;

	public void indexDoc(List<Doc> docs) throws Exception {

		// 分词器, 作用是将一段文本分词。 analyzer 是一个抽象类，具体的切分词规则由子类实现
		Analyzer analyzer = new StandardAnalyzer();

		// 索引创建配置
		IndexWriterConfig iwConfig = new IndexWriterConfig(analyzer);
		iwConfig.setOpenMode(IndexWriterConfig.OpenMode.CREATE);

		// 索引目录
		Directory fsDirectory = FSDirectory.open(Paths.get(PropertyUtil.getProperty("indexPath")));

		// 构造索引写入的对象
		IndexWriter indexWriter = new IndexWriter(fsDirectory, iwConfig);

		// 分组统计检索目录
		Directory taoDirectory = FSDirectory.open(Paths.get(PropertyUtil.getProperty("facetIndexPath")));
		DirectoryTaxonomyWriter taxoWriter = new DirectoryTaxonomyWriter(taoDirectory);

		System.out.println("目标文档数量：" + docs.size());
		int i = 0;
		try {
			for (i = 0; i < docs.size(); i++) {

				SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");

				Doc document = (Doc) docs.get(i);
				Document indexDoc = new Document();

				// 索引字段
				indexDoc.add(new IntField("docid", document.getDocid() != null ? document.getDocid() : 0, Store.YES));
				indexDoc.add(new TextField("doctitle", document.getDoctitle() != null ? document.getDoctitle() : "", Store.YES));
				indexDoc.add(new TextField("doccontent", document.getDoccontent() != null ? document.getDoccontent().replaceAll("<[^>]*>", "").replaceAll("&nbsp;", "").replaceAll(" ", "") : "", Store.YES));
				indexDoc.add(new LongField("docreltime", Long.parseLong(sdf.format(document.getDocreltime() != null ? document.getDocreltime() : new Date())), Store.YES));
				indexDoc.add(new StringField("docpubUrl", document.getDocpuburl() != null ? document.getDocpuburl() : "", Store.YES));
				indexDoc.add(new StringField("channelid", document.getChannelid() != null ? document.getChannelid() : "", Store.YES));
				indexDoc.add(new StringField("chnldesc", document.getChnldesc() != null ? document.getChnldesc() : "", Store.YES));
				indexDoc.add(new StringField("siteid", document.getSiteid() != null ? document.getSiteid() : "", Store.YES));
				indexDoc.add(new TextField("temp1", document.getTemp1() != null ? document.getTemp1() : "", Store.YES));
				indexDoc.add(new TextField("temp2", document.getTemp2() != null ? document.getTemp2() : "", Store.YES));
				indexDoc.add(new TextField("temp3", document.getTemp3() != null ? document.getTemp3() : "", Store.YES));

				// 统计字段
				indexDoc.add(new FacetField("chnldesc", document.getChnldesc() != null ? document.getChnldesc() : ""));
				indexDoc.add(new FacetField("channelid", document.getChannelid() != null ? document.getChannelid() : ""));
				// indexDoc.add(new FacetField("siteid", document.getSiteid() != null ? document.getSiteid() : ""));

				// 排序和时间区间过滤字段
				// 标题相关度排序
				indexDoc.add(new SortedDocValuesField("doctitle", new BytesRef((document.getDoctitle() != null ? document.getDoctitle() : "").getBytes())));
				// 时间变为number区间筛选
				indexDoc.add(new NumericDocValuesField("docreltime", Long.parseLong(sdf.format(document.getDocreltime() != null ? document.getDocreltime() : new Date()))));

				// 得分干预字段
				indexDoc.add(new NumericDocValuesField("timeAsDay", (int) ((document.getDocreltime() != null ? document.getDocreltime() : new Date()).getTime() / (1000 * 3600 * 24))));

				FacetsConfig config = new FacetsConfig();
				indexWriter.addDocument(config.build(taxoWriter, indexDoc));
			}
			if (i == docs.size()) {
				System.out.println("索引创建数量：" + i);
			}
		} catch (Exception e) {
			System.err.println("创建索引过程出现问题！！！已创建文档索引数量：" + i);
		} finally {
			indexWriter.commit();
			indexWriter.close();
			taxoWriter.close();
		}
	}

	public String mkIndex() {
		System.out.println("Begin create all index...");
		List<Doc> docs = d.findAll();
		delIndex();
		long begin = System.currentTimeMillis();
		try {
			indexDoc(docs);
		} catch (Exception e) {
			e.printStackTrace();
		}
		long end = System.currentTimeMillis();
		System.out.println("Create all index。耗时：" + (end - begin) + "ms");
		return "创建成功，索引耗时：" + (end - begin) + "ms";
	}

	public String delIndex() {
		System.out.println("Begin delete all index...");
		long begin = System.currentTimeMillis();
		Analyzer analyzer = new StandardAnalyzer();
		IndexWriterConfig iwConfig = new IndexWriterConfig(analyzer);
		iwConfig.setOpenMode(IndexWriterConfig.OpenMode.CREATE);
		try {
			Directory fsDirectory = FSDirectory.open(Paths.get(PropertyUtil.getProperty("indexPath"), new String[0]));
			IndexWriter indexWriter = new IndexWriter(fsDirectory, iwConfig);
			indexWriter.deleteAll();
			indexWriter.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
		long end = System.currentTimeMillis();
		System.out.println("Deleted all index。耗时：" + (end - begin) + "ms");
		return "删除成功，索引耗时：" + (end - begin) + "ms";
	}

	@SuppressWarnings({ "deprecation", "unlikely-arg-type", "unused" })
	public String search(HttpServletRequest request, BindingResult bindingResult, Search search, PageUtil page) {

		// 构造返回json
		JSONObject rs = new JSONObject();
		rs.put("data", null);
		rs.put("msg", "操作失败！");
		rs.put("isSuccess", false);

		// 参数验证
		if (bindingResult.hasErrors()) {

			// jdk1.7
			for (int i = 0; i < bindingResult.getAllErrors().size(); i++) {
				FieldError fieldError = (FieldError) bindingResult.getAllErrors().get(i);
				// 属性
				String field = fieldError.getField();
				// 错误信息
				String message = fieldError.getDefaultMessage();
				rs.put("msg", "操作失败！" + field + ":" + message);
			}

			// jdk1.8
			// bindingResult.getAllErrors().forEach((error) -> {
			// FieldError fieldError = (FieldError) error;
			// // 属性
			// String field = fieldError.getField();
			// // 错误信息
			// String message = fieldError.getDefaultMessage();
			// rs.put("msg", "操作失败！" + field + ":" + message);
			// });
			return rs.toString();
		}

		// 处理检索词
		search.setSearchword(search.getSearchword().replaceAll("&nbsp;", "").replaceAll(" ", ""));

		// 处理时间
		SimpleDateFormat fmt = new SimpleDateFormat("yyyyMMddHHmmss");
		Calendar calendar = Calendar.getInstance();
		if (search.getTime() != null && !search.getTime().equals("")) {
			if (search.getTime().equals("all")) {
				search.setStarttime("20000101000000");
			} else if (search.getTime().equals("year")) {
				calendar.add(Calendar.DATE, -365);
				search.setStarttime(fmt.format(calendar.getTime()));
			} else if (search.getTime().equals("halfyear")) {
				calendar.add(Calendar.DATE, -180);
				search.setStarttime(fmt.format(calendar.getTime()));
			} else if (search.getTime().equals("month")) {
				calendar.add(Calendar.MONTH, -1);
				search.setStarttime(fmt.format(calendar.getTime()));
			} else if (search.getTime().equals("week")) {
				calendar.add(Calendar.DATE, -7);
				search.setStarttime(fmt.format(calendar.getTime()));
			} else if (search.getTime().equals("date")) {
				calendar.add(Calendar.DATE, -1);
				search.setStarttime(fmt.format(calendar.getTime()));
			}
		}
		if (search.getStarttime() == null || search.getStarttime().equals("")) {
			search.setTime("all");
			search.setStarttime("20000101000000");
		} else {
			try {
				fmt.parse(search.getStarttime());
			} catch (ParseException e) {
				rs.put("msg", "操作失败，开始时间不合法");
				return rs.toString();
			}
		}
		if (search.getEndtime() == null || search.getEndtime().equals("")) {
			search.setEndtime(fmt.format(new Date()));
		} else {
			try {
				fmt.parse(search.getEndtime());
			} catch (ParseException e) {
				rs.put("msg", "操作失败，结束时间不合法");
				return rs.toString();
			}
		}

		// 处理检索类别
		// System.out.println("检索类别:"+search.getChannelid()+"");
		if (StringUtil.isEmpty(search.getChannelid() + "")) {
			search.setChannelid(0);
		} else {
			if (sc.getSearchArray().size() <= search.getChannelid()) {
				search.setChannelid(0);
			}
		}

		// 处理分页
		if (page.getPageCur() == null || page.getPageCur().equals("")) {
			page.setPageCur(0);
		}
		if (page.getPageSize() == null || page.getPageSize().equals("")) {
			page.setPageSize(10);
		}

		// 处理排序
		Sort sort = new Sort();

		// 处理排序的倒正序
		boolean bl = true;
		if (page.getOrder() != null && !page.getOrder().isEmpty() && page.getOrder().equals("asc")) {
			bl = false;
		} else {
			page.setOrder("desc");
		}

		// 处理排序字段
		if (page.getSort() == null || page.getSort().equals("") || page.getSort().equals("relevance")) {
			page.setSort(sc.getSearchArray().get(search.getChannelid()).getSort());
		}
		if (page.getSort() != null && !page.getSort().isEmpty()) {
			String searchsort = page.getSort();
			String searchsorts[] = sc.getSearchArray().get(search.getChannelid()).getSorts().split(",");
			for (String string : searchsorts) {
				if (searchsort.equals(string)) {
					page.setSort(string);
					break;
				} else {
					page.setSort(sc.getSearchArray().get(search.getChannelid()).getSort());
				}
			}
			if (page.getSort().equals("doctitle")) {
				sort.setSort(new SortField[] { SortField.FIELD_SCORE, new SortField(page.getSort(), SortField.Type.STRING, true) });
			}
			if (page.getSort().equals("docreltime")) {
				sort.setSort(new SortField(page.getSort(), SortField.Type.LONG, bl));
			}
			if (page.getSort().equals("docid")) {
				sort.setSort(new SortField(page.getSort(), SortField.Type.LONG, bl));
			}
			if (page.getSort().equals("chnldesc")) {
				sort.setSort(new SortField(page.getSort(), SortField.Type.STRING, bl));
			}
		}

		// 处理检索字段
		if (search.getField() == null || search.getField().equals("")) {
			search.setField(sc.getSearchArray().get(search.getChannelid()).getField());
		} else {
			String searchFiled1[] = search.getField().split(",");
			String searchFileds[] = sc.getSearchArray().get(search.getChannelid()).getFields().split(",");
			ArrayList<String> searchFiled3 = new ArrayList<>();
			for (String string : searchFiled1) {
				for (String string2 : searchFileds) {
					if (string.equals(string2)) {
						searchFiled3.add(string);
					}
				}
			}
			if (searchFiled3.size() == 0) {
				search.setField(sc.getSearchArray().get(search.getChannelid()).getField());
			} else {
				search.setField(StringUtils.join(searchFiled3, ","));
			}
		}

		// 分组信息json数组
		JSONArray jsonArrayGroup = new JSONArray();
		// 总记录数
		int totalHits = 0;
		try {

			long begin = System.currentTimeMillis();
			Directory indexDirectory = FSDirectory.open(Paths.get(PropertyUtil.getProperty("indexPath"), new String[0]));
			DirectoryReader indexReader = DirectoryReader.open(indexDirectory);
			IndexSearcher indexSearch = new IndexSearcher(indexReader);

			BooleanQuery booleanQuery = new BooleanQuery();
			ArrayList<Doc> resultDocs = new ArrayList<Doc>();
			Analyzer analyzer = new StandardAnalyzer();

			// 增加配置文件条件
			if (sc.getSearchArray().get(search.getChannelid()).getChannelid() != null && !sc.getSearchArray().get(search.getChannelid()).getChannelid().equals(""))
				booleanQuery.add(new MultiFieldQueryParser("channelid".split(","), analyzer).parse(sc.getSearchArray().get(search.getChannelid()).getChannelid().replaceAll(",", " or ")), Occur.MUST);
			if (sc.getSearchArray().get(search.getChannelid()).getSiteid() != null && !sc.getSearchArray().get(search.getChannelid()).getSiteid().equals(""))
				booleanQuery.add(new MultiFieldQueryParser("siteid".split(","), analyzer).parse(sc.getSearchArray().get(search.getChannelid()).getSiteid().replaceAll(",", " or ")), Occur.MUST);

			// 根据检索类型增加检索词条件
			String searchFiled[] = search.getField().split(",");
			String searchWord[] = search.getSearchword().split(",");
			if (search.getType() != null && !search.getType().trim().equals("") && search.getType().trim().equals("key") && searchFiled.length == searchWord.length) {
				for (int i = 0; i < searchFiled.length; i++) {
					booleanQuery.add(new MultiFieldQueryParser(searchFiled[i].split(","), analyzer).parse(searchWord[i]), Occur.MUST);
				}
			} else
				booleanQuery.add(new MultiFieldQueryParser(search.getField().split(","), analyzer).parse(search.getSearchword()), Occur.MUST);

			// 增加权值判断条件
			int day = (int) (new Date().getTime() / (24 * 60 * 60 * 1000));
			Query customScoreQuery = new RecencyBoostCustomScoreQuery(booleanQuery, 2.0, day, 1 * 365, "timeAsDay");

			// 时间整型数区间
			booleanQuery.add(NumericRangeQuery.newLongRange("docreltime", Long.parseLong(search.getStarttime()), Long.parseLong(search.getEndtime()), true, true), Occur.MUST);

			// 统计信息，当检索结果集不为空时进行
			TopDocs topDoc = indexSearch.search(booleanQuery, 1);
			if (topDoc.totalHits > 0) {
				Directory taxoDirectory = FSDirectory.open(Paths.get(PropertyUtil.getProperty("facetIndexPath")));
				TaxonomyReader taxoReader = new DirectoryTaxonomyReader(taxoDirectory);
				FacetsConfig config = new FacetsConfig();

				// 相应的Collector是必不可少的
				FacetsCollector facetsCollector = new FacetsCollector();
				indexSearch.search(booleanQuery, facetsCollector);
				Facets facets = new FastTaxonomyFacetCounts(taxoReader, config, facetsCollector);

				// // 统计全部可统计统计字段，需要在生成索引时定义统计字段
				// List<FacetResult> results = facets.getAllDims(10);
				// for (FacetResult tmp : results) {
				// System.out.println("统计信息:" + tmp);
				// }

				// 统计单个统计字段信息，需要在生成索引时定义统计字段
				JSONObject jsonOjectGroup = null;
				LabelAndValue[] labelValues = null;
				labelValues = facets.getTopChildren(Integer.parseInt(sc.getSearchArray().get(search.getChannelid()).getGroupbynum()), sc.getSearchArray().get(search.getChannelid()).getGroupby()).labelValues;

				for (LabelAndValue labelAndValue : labelValues) {
					jsonOjectGroup = new JSONObject();
					jsonOjectGroup.put("name", labelAndValue.label);
					jsonOjectGroup.put("num", labelAndValue.value);
					jsonArrayGroup.add(jsonOjectGroup);
				}
			}

			// 增加分组统计id条件
			if (search.getGroupid() != null && !search.getGroupid().trim().equals("")) {
				booleanQuery.add(new TermQuery(new Term(sc.getSearchArray().get(search.getChannelid()).getGroupby(), search.getGroupid())), Occur.MUST);
			}

			// 查询
			TopFieldCollector c = TopFieldCollector.create(sort, page.getPageCur() * page.getPageSize() + page.getPageSize(), false, false, false);
			indexSearch.search(customScoreQuery, null, c);
			ScoreDoc[] scoreDocs = c.topDocs(page.getPageCur() * page.getPageSize(), page.getPageSize()).scoreDocs;
			totalHits = c.getTotalHits();

			if (totalHits != 0) {

				Highlighter highlighter = new Highlighter(new SimpleHTMLFormatter("<span style='color:red'>", "</span>"), new QueryScorer(booleanQuery));
				int fragmentSize = Integer.parseInt(PropertyUtil.getProperty("fragmentSize"));
				highlighter.setTextFragmenter(new SimpleFragmenter(fragmentSize));

				for (int i = 0; i < scoreDocs.length; i++) {

					Doc doc = new Doc();
					Document targetDoc = indexSearch.doc(scoreDocs[i].doc);
					TokenStream tokenStream;

					// 标题高亮
					String doctitle = targetDoc.get("doctitle");
					tokenStream = analyzer.tokenStream("doctitle", new StringReader(doctitle));
					String highlightTitle = highlighter.getBestFragment(tokenStream, doctitle);
					if (highlightTitle == null) {
						doc.setDoctitle(doctitle);
					} else {
						if (!"4".equals(search.getSearchword())) {
							highlightTitle = highlightTitle.replace("<span style='color:red'>4</span>", "4");
						}
						doc.setDoctitle(highlightTitle);
					}

					// 正文高亮
					String doccontent = targetDoc.get("doccontent");
					tokenStream = analyzer.tokenStream("doccontent", new StringReader(doccontent));
					String highlightContent = highlighter.getBestFragment(tokenStream, doccontent);
					if (highlightContent == null) {
						if ((doccontent != null) && (doccontent.length() > fragmentSize)) {
							doccontent = doccontent.substring(0, fragmentSize);
						}
						doc.setDoccontent(doccontent);
					} else {
						if (!"4".equals(search.getSearchword())) {
							highlightContent = highlightContent.replace("<span style='color:red'>4</span>", "4");
						}
						doc.setDoccontent(highlightContent);
					}

					doc.setDocid(Integer.parseInt(targetDoc.get("docid")));
					doc.setDocreltime(fmt.parse(targetDoc.get("docreltime")));
					doc.setDocpuburl(targetDoc.get("docpubUrl"));
					doc.setChannelid(targetDoc.get("channelid"));
					doc.setChnldesc(targetDoc.get("chnldesc"));
					doc.setSiteid(targetDoc.get("siteid"));
					doc.setTemp1(targetDoc.get("temp1"));
					doc.setTemp2(targetDoc.get("temp2"));
					doc.setTemp3(targetDoc.get("temp3"));

					resultDocs.add(doc);
				}
			}
			long end = System.currentTimeMillis();

			// // 相关信息
			// System.out.println("检索类别：" + search.getChannelid());
			// System.out.println("检索词汇：" + search.getSearchword());
			// System.out.println("开始时间：" + search.getStarttime());
			// System.out.println("结束时间：" + search.getEndtime());
			// System.out.println("检索字段：" + search.getField());
			// System.out.println("排序字段：" + page.getOrder());
			// System.out.println("排序方式：" + page.getSort());
			// System.out.println("统计ID：" + search.getGroupid());
			// System.out.println("总记录数：" + totalHits);

			if (resultDocs != null) {
				JsonConfig jsonConfig = new JsonConfig(); // 建立配置文件
				jsonConfig.registerJsonValueProcessor(Date.class, new JsonDateValueProcessor("yyyy-MM-dd HH:mm:ss"));
				jsonConfig.setExcludes(new String[] { "user" });
				JSONArray jsarray = (JSONArray) JSONSerializer.toJSON(resultDocs, jsonConfig);
				JSONObject jsonObject = new JSONObject();
				jsonObject.put("data", jsarray);
				jsonObject.put("group", jsonArrayGroup);
				jsonObject.put("search", search);
				page.setTotalElements(totalHits);
				jsonObject.put("page", page);
				jsonObject.put("time", end - begin);

				rs.put("msg", "查询成功！");
				rs.put("isSuccess", true);
				rs.put("data", jsonObject);
				return rs.toString();
			} else {
				rs.put("msg", "查询成功，暂无数据！");
				rs.put("isSuccess", true);
			}

		} catch (Exception e) {
			e.printStackTrace();
		}

		// 返回结果
		return rs.toString();
	}
}
